﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Interface;
using LogicAnalyzerCommon;

namespace SimpleLogicAnalyzer
{
    public class LogicAnalyzer : ILogicAnalyzer
    {
        private readonly long[] _frequenciesSupported = new long[] { 2000000, 200000, 20000, 2000 };
        private readonly long[] _sampleSizesSupported = new long[] { 1024, 2048, 4096, 8192, 16384, 32768 };

        internal const int NumberOfChannels = 8;

        private SettingsControl _settingsControl;

        public IEnumerable<long> FrequenciesSupported { get { return _frequenciesSupported; } }
        public IEnumerable<long> SampleSizesSupported { get { return _sampleSizesSupported; } }
        public UserControl SettingsPanel { get { return _settingsControl; } }
        public string Name { get { return "Simple Logic Analyzer"; } }

        public string PrettyPrintSampleSettings(IDictionary<string, string> sampleSettings)
        {
            if (string.IsNullOrEmpty(GetPortName(sampleSettings)))
            {
                return string.Format("{0}, {1} samples, {2}", (_frequenciesSupported[GetSampleFrequencyIndex(sampleSettings)]).PrettyPrintFrequency(), _sampleSizesSupported[GetSampleSizeIndex(sampleSettings)], GetTriggerMode(sampleSettings).AsString());
            }
            else
            {
                return string.Format("{0}, {1}, {2} samples, {3}", GetPortName(sampleSettings), (_frequenciesSupported[GetSampleFrequencyIndex(sampleSettings)]).PrettyPrintFrequency(), _sampleSizesSupported[GetSampleSizeIndex(sampleSettings)], GetTriggerMode(sampleSettings).AsString());
            }
        }

        public IEnumerable<ICommunication> CommunicationDevices
        {
            get;
            private set;
        }

        public LogicAnalyzer(IDictionary<string, string> sampleSettings, IEnumerable<ICommunication> communicationDevices)
        {
            this.CommunicationDevices = communicationDevices;

            _settingsControl = new SettingsControl(this, sampleSettings);
            //_acquireSampleSettings = new Dictionary<string, string>();
        }

        public IData Load(Stream stream)
        {
            byte[] data = new byte[stream.Length];
            stream.Read(data, 0, (int)stream.Length);

            const int HeaderLength = 13;
            byte[] sampleData = new byte[data.Length - (HeaderLength + 1)];

            Array.Copy(data, HeaderLength/*skip header at start*/, sampleData, 0, data.Length - (HeaderLength + 1/* skip checksum at end */));

            IDictionary<string, string> acquireSampleSettings = CreateSampleSettingsFromSlaFile(data);

            // TODO check checksum
            //this.Checksum = data[data.Length - 1];

            return new SamplePacket(acquireSampleSettings, sampleData);
        }

        /// <summary>
        /// Save to simple logic analyzer format
        /// </summary>
        /// <param name="samplePacket"></param>
        /// <param name="stream"></param>
        public void Save(IData samplePacket, Stream stream)
        {
            // Byte 0: sample size in 256 blocks
            byte sampleSizeIn256ByteBlocks = 0;
            switch (GetSampleSizeIndex(samplePacket.SampleSettings))
            {
                case 0: sampleSizeIn256ByteBlocks = 4; break; // 1024 bytes
                case 1: sampleSizeIn256ByteBlocks = 8; break; // 2048 bytes
                case 2: sampleSizeIn256ByteBlocks = 16; break; // 4096 bytes
                case 3: sampleSizeIn256ByteBlocks = 32; break; // 8192 bytes
                case 4: sampleSizeIn256ByteBlocks = 64; break; // 16384 bytes
                case 5: sampleSizeIn256ByteBlocks = 128; break; // 32768 bytes
                default: throw new InvalidSettingsException(SettingsControl.SampleSizeIndexKey);
            }
            stream.WriteByte(sampleSizeIn256ByteBlocks);
            // Byte 1: 0
            stream.WriteByte(0);
            // Byte 2: Sample frequency: 0 = 2 MHz, 1 = 200 kHz, 2 = 20 kHz, 3 = 2 kHz
            stream.WriteByte(GetSampleFrequencyIndex(samplePacket.SampleSettings));
            // Byte 3: Trigger mode: 0 = autocmp, 1 = autoedge, 2 = manual
            stream.WriteByte((byte)GetTriggerMode(samplePacket.SampleSettings));
            // Byte 4 - 11 (ch0..7): autocmp 0 = L, 1 = H; autoedge 0 = falling, 1 = rising
            byte autoCompareTriggerLevel = GetAutoCompareTriggerLevel(samplePacket.SampleSettings);
            for (byte channel = 0; channel < NumberOfChannels; channel++)
            {
                stream.WriteByte((byte)(autoCompareTriggerLevel & (1 << channel)));
            }
            // Byte 12: Autoedge channel: 0 = 0, ... , 7 = 7
            stream.WriteByte((byte)GetAutoEdgeChannelIndex(samplePacket.SampleSettings));

            // Write the data
            stream.Write(samplePacket.SampleData.ToArray(), 0, samplePacket.SampleData.Count());

            // Write the check sum
            stream.WriteByte(samplePacket.SampleData.Crc8Dallas());
        }

        private string GetPortName(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.PortNameKey);
            if (!sampleSettings.ContainsKey(key) ||
                string.IsNullOrEmpty(sampleSettings[key]))
            {
                return string.Empty;
            }
            return sampleSettings[key];
        }

        public void Acquire(BackgroundWorker backgroundWorker, DoWorkEventArgs doWorkEventArgs)
        {
            IDictionary<string, string> acquireSampleSettings = new Dictionary<string, string>();

            ICommunication serialPort = null;
#if DEBUG
            Stopwatch stopwatch = null;
#endif // DEBUG
            try
            {
                IAcquireArgument argument = doWorkEventArgs.Argument as IAcquireArgument;
                IDictionary<string, string> sampleSettings = argument.SampleSettings;

                foreach (string key in argument.SampleSettings.Keys.Where(k => k.StartsWith(SettingsControl.SimpleLogicAnalyzer)))
                {
                    acquireSampleSettings[key] = argument.SampleSettings[key];
                }

                if(string.IsNullOrEmpty(GetPortName(sampleSettings)))
                {
                    throw new InvalidSettingsException("No port defined");
                }

                serialPort = GetCommunicationDevice(GetPortName(sampleSettings));

                byte[] writeBuffer = new byte[20];

                // instruct analyser to gather data, wait for it to complete & retrieve data.

                // Instantiate port to analyser
                //using (SerialPort serialPort = new SerialPort(_fileSettings.PortName, 38400, Parity.None, 8, StopBits.One))
                {
                    serialPort.ReadTimeout = 1000;
                    // Open the port for communications
                    serialPort.Open();

                    // Send wake up string
                    serialPort.Write(new byte[] { (byte)'Q' }, 0, 1);  // Send reset; no reply from analyzer
                    System.Threading.Thread.Sleep(3000);
                    serialPort.Write(new byte[] { (byte)'I' }, 0, 1);  // Wakeup; expected response: 'SLA_ANTONIAK_V1'

                    string expectedResponse = "SLA_ANTONIAK_V1";
                    StringBuilder receivedResponse = new StringBuilder(expectedResponse.Length);
                    for (int index = 0; index < expectedResponse.Length; index++)
                    {
                        receivedResponse.Append((char)serialPort.ReadByte());
                    }

                    if (receivedResponse.ToString() != expectedResponse)
                    {
                        throw new UnexpectedResponseException(string.Format("The analyzer responded with '{0}' while '{1}' was excpected.", receivedResponse.ToString(), expectedResponse));
                    }

                    // Create & Send Query-frame which sets the sample parameters and starts aquisition.
                    writeBuffer[0] = 65;                                        // todo ** = check & correct domain, * = verify it works okay
                    switch (GetTriggerMode(sampleSettings))
                    {
                        case TriggerMode.Manual:    // tested, okay.
                            writeBuffer[1] = 1;
                            writeBuffer[2] = 0;
                            writeBuffer[3] = 0;
                            break;
                        case TriggerMode.AutoCmp:   // tested, okay.
                            writeBuffer[1] = 2;
                            writeBuffer[2] = 0;
                            writeBuffer[3] = GetAutoCompareTriggerLevel(sampleSettings);
                            break;
                        case TriggerMode.AutoEdge:
                            writeBuffer[1] = 3;
                            writeBuffer[2] = GetAutoEdgeChannelIndex(sampleSettings);                                   // ** edge pin
                            writeBuffer[3] = (byte)((GetAutoEdgeDirection(sampleSettings) == TriggerEdge.Falling) ? 1 : 0);   // * rising/falling edge
                            break;
                    }
                    writeBuffer[4] = (byte)(GetSampleFrequencyIndex(sampleSettings) + 1);                                  // tested, okay
                    writeBuffer[5] = (byte)(GetSampleSizeIndex(sampleSettings) + 1);                                       // tested, okay.
                    writeBuffer[6] = (byte)(writeBuffer[1] + writeBuffer[2] + writeBuffer[3] + writeBuffer[4] + writeBuffer[5]);    // sum
                    serialPort.Write(writeBuffer, 0, 7);

                    Console.WriteLine(string.Format("Sampling params: {0} {1} {2} {3} {4}",
                        writeBuffer[1], writeBuffer[2], writeBuffer[3], writeBuffer[4], writeBuffer[5]));

                    System.Threading.Thread.Sleep(10);
                    Console.WriteLine("Wait for sampling to complete");
                    System.Threading.Thread.Sleep(100);

                    // now we wait for the sampling to end
                    while (serialPort.BytesToRead == 0)
                    {
                        if (backgroundWorker.CancellationPending)
                        {
                            doWorkEventArgs.Cancel = true;
                            return;
                        }
                        System.Threading.Thread.Sleep(100);
                    }

                    int block = 0;   // we get the first (1k) block automatically when sampling is complete
                    int nrBlocks = 1;   // nr of 1k blocks we expect
                    switch (GetSampleSizeIndex(sampleSettings))
                    {
                        case 0: nrBlocks = 1; break;
                        case 1: nrBlocks = 2; break;
                        case 2: nrBlocks = 4; break;
                        case 3: nrBlocks = 8; break;
                        case 4: nrBlocks = 16; break;
                        case 5: nrBlocks = 32; break;
                    }

                    Console.WriteLine(string.Format("receive block {0}", block));
                    List<byte> analyserData = new List<byte>(nrBlocks * 1024);
#if DEBUG
                    stopwatch = new Stopwatch();
                    stopwatch.Start();
#endif // DEBUG
                    const int BufferSize = 1024;
                    byte[] readBuffer = new byte[BufferSize];

                    //int i = 0;
                    for (; ; )
                    {
                        // Check for cancellation by user
                        if (backgroundWorker.CancellationPending)
                        {
                            doWorkEventArgs.Cancel = true;
                            return;
                        }

                        // Read the data
                        int bytesRead = serialPort.Read(readBuffer, 0, BufferSize);
                        analyserData.AddRange(readBuffer.Take(bytesRead));

                        // Report progress
                        int percentProgress = (int)(100.0 * (analyserData.Count / (nrBlocks * 1024.0)));
                        backgroundWorker.ReportProgress(percentProgress);

                        // When the first 1024 bytes are received,
                        // send command for the next 1024 bytes,
                        // and so on for each following 1024 bytes.
                        if (analyserData.Count % 1024 == 0)
                        {
                            block++;
                            if (block < nrBlocks)
                            {
                                Console.WriteLine(string.Format("receive block {0}", block));
                                writeBuffer[0] = (byte)block;
                                serialPort.Write(writeBuffer, 0, 1);
                            }
                            else
                            {
                                break;
                            }
                        }
                    }

                    Console.WriteLine(string.Format("received all {0} bytes", analyserData.Count));

                    // get checksum and reset analyser
                    Console.WriteLine(string.Format("bytetoread @1 {0}", serialPort.BytesToRead));

                    serialPort.Write(new byte[] { (byte)'C' }, 0, 1);

                    byte receivedCrc = (byte)serialPort.ReadByte();
                    byte calculatedCrc = analyserData.Crc8Dallas();

                    if (receivedCrc != calculatedCrc)
                    {
                        throw new CrcException(string.Format("!!! CRC Error received: {0}, calculated: {1}", receivedCrc, calculatedCrc));
                    }
                    Console.WriteLine("CRC Okay.");

                    System.Threading.Thread.Sleep(100);
                    Console.WriteLine("communications done");

                    doWorkEventArgs.Result = new SamplePacket(acquireSampleSettings, analyserData.ToArray());
                }
            }
            finally
            {
                // Close the port
                if (serialPort != null)
                {
                    serialPort.Close();
                }

#if DEBUG
                if (stopwatch != null)
                {
                    stopwatch.Stop();
                    Console.WriteLine("Time: {0}", stopwatch.Elapsed);
                }
#endif // DEBUG
            }
        }

        private IDictionary<string,string> CreateSampleSettingsFromSlaFile(IList<byte> data)
        {
            IDictionary<string,string> sampleSettings = new Dictionary<string,string>();

            // Byte 0: sample size index
            byte sampleSizeIndex = 0;
            switch (data[0])
            {
                case 4: sampleSizeIndex = 0; break;
                case 8: sampleSizeIndex = 1; break;
                case 16: sampleSizeIndex = 2; break;
                case 32: sampleSizeIndex = 3; break;
                case 64: sampleSizeIndex = 4; break;
                case 128: sampleSizeIndex = 5; break;
                default: throw new InvalidSettingsException("Sample Size (byte 0)");
            }
            sampleSettings.Add(Key(SettingsControl.SampleSizeIndexKey), sampleSizeIndex.ToString());

            // Byte 1: should be 0

            // Byte 2: Frequency index
            sampleSettings.Add(Key(SettingsControl.SampleFrequencyIndexKey), data[2].ToString());

            // Byte 3: trigger mode
            TriggerMode triggerMode = TriggerMode.Unknown;
            switch (data[3])
            {
                case 0:
                    triggerMode = TriggerMode.AutoCmp;
                    break;
                case 1:
                    triggerMode = TriggerMode.AutoEdge;
                    break;
                case 2:
                    triggerMode = TriggerMode.Manual;
                    break;
                default:
                    throw new InvalidSettingsException(string.Format("Unknown Trigger Mode (byte 3): '{0}'. It should be 0, 1 or 2.", data[3]));
            }
            sampleSettings.Add(Key(SettingsControl.TriggerModeKey), Enum.GetName(typeof(TriggerMode), triggerMode));

            // Byte 4..11: trigger level per channel
            int filter = 1;
            byte autoCompareTriggerLevel = 0;
            for (int channel = 0; channel < LogicAnalyzer.NumberOfChannels; channel++)
            {
                if (data[channel + 4] == 1)
                {
                    autoCompareTriggerLevel |= (byte)(filter <<= channel);
                }
            }
            sampleSettings.Add(Key(SettingsControl.AutoCompareTriggerLevelKey), autoCompareTriggerLevel.ToString());

            // Byte 12: auto edge channel (0..7)
            if (data[12] > 8)
            {
                throw new InvalidSettingsException(string.Format("Invalid Auto Edge Channel Index: '{0}'. It should be between 0 and 7."));
            }
            sampleSettings.Add(Key(SettingsControl.AutoEdgeChannelIndexKey), data[12].ToString());

            return sampleSettings;
        }

        private ICommunication GetCommunicationDevice(string portName)
        {
            foreach (ICommunication communicationDevice in this.CommunicationDevices)
            {
                if (communicationDevice.PortNames.Contains(portName))
                {
                    if (portName.StartsWith("COM"))
                    {
                        communicationDevice.PortName = portName;
                    }

                    return communicationDevice;
                }
            }
            return null;
        }

        public long GetSampleFrequency(IDictionary<string, string> sampleSettings)
        {
            string value;
            string key = Key(SettingsControl.SampleFrequencyIndexKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, "0");
            if (sampleSettings.TryGetValue(key, out value))
            {
                return _frequenciesSupported[long.Parse(value)];
            }
            return 0L;
        }

        private TriggerMode GetTriggerMode(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.TriggerModeKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, Enum.GetName(typeof(TriggerMode), TriggerMode.Manual));
            return (TriggerMode)Enum.Parse(typeof(TriggerMode), sampleSettings[key]);
        }

        private byte GetAutoCompareTriggerLevel(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.AutoCompareTriggerLevelKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, "0");
            return byte.Parse(sampleSettings[key]);
        }

        private byte GetAutoEdgeChannelIndex(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.AutoEdgeChannelIndexKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, "0");
            return byte.Parse(sampleSettings[key]);
        }

        private TriggerEdge GetAutoEdgeDirection(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.AutoEdgeDirectionKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, Enum.GetName(typeof(TriggerEdge), TriggerEdge.Rising));
            return (TriggerEdge)Enum.Parse(typeof(TriggerEdge), sampleSettings[key]);
        }

        private byte GetSampleFrequencyIndex(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.SampleFrequencyIndexKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, "0");
            return byte.Parse(sampleSettings[key]);
        }

        private byte GetSampleSizeIndex(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.SampleSizeIndexKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, "0");
            return byte.Parse(sampleSettings[key]);
        }

        private static string Key(string key)
        {
            return string.Format("{0}.{1}", SettingsControl.SimpleLogicAnalyzer, key);
        }
    }
}
